﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Core.Models;
using Service.AI;
using Service.Model.Context;
using ShortBus;

namespace Service.Workflow.LearningWeight.Command
{
    public class LearnWeightCommandHandler : IAsyncQueryHandler<LearnWeightCommand, LearnWeightCommandResponse>
    {
        private readonly ServiceDbContext dbContext;

        public LearnWeightCommandHandler(ServiceDbContext serviceDbContext)
        {
            this.dbContext = serviceDbContext;
        }

        public async Task<LearnWeightCommandResponse> HandleAsync(LearnWeightCommand command)
        {
            var gameState = new GameState();
            var moves = command.pgn.Split(' ').Select(m => m.Contains('.') ? m.Substring(m.IndexOf('.') + 1) : m).ToList();
            int nbMove = 0;
            try
            {
                foreach (var move in moves)
                {
                    var coordinateMove = gameState.GetMoveFromAlgebraicNotation(move);
                    var weight = new List<double>() {0.5, 0.25, 0.1, 0.05, 0.1};
                    var LearningAlphaBeta = new LearningAlphaBeta(gameState,
                        weight,
                        coordinateMove.WriteFullMove());


                    var alphaBetaResult = LearningAlphaBeta.StartIteration(3, true);
                    if(alphaBetaResult.Item2 != null)
                    {
                        var nbIteration = 1;
                        while (alphaBetaResult.Item1.WriteFullMove() != coordinateMove.WriteFullMove() && nbIteration < 5)
                        {
                            weight = LearnWeight(weight, alphaBetaResult.Item2, alphaBetaResult.Item3);
                            LearningAlphaBeta = new LearningAlphaBeta(gameState, weight, coordinateMove.WriteFullMove());

                            alphaBetaResult = LearningAlphaBeta.StartIteration(3, true);

                            nbIteration++;
                        }

                        var weightArray = weight.ToArray();

                        var learnedWeight = new Model.LearningWeight()
                        {
                            WeightPieceValue = weightArray[0],
                            WeightPawnAdvancement = weightArray[1],
                            WeightMobility = weightArray[2],
                            WeightCoordinationPiece = weightArray[3],
                            WeightDiagonalsAndFiles = weightArray[4]
                        };

                        dbContext.LearningWeights.Add(learnedWeight);

                        await dbContext.SaveChangesAsync();
                    }
                    gameState.MovePiece(move);
                    nbMove++;
                }
            }
            catch (Exception e)
            {
                nbMove += 1000;
                throw new Exception(e.ToString());
            }

            return new LearnWeightCommandResponse() { MoveLearned = nbMove};
        }

        private List<double> LearnWeight(List<double> currentWeight, List<double> bestMoveScore, List<double> bestHeuristicScore)
        {
            var newWeight = new List<double>();
            var bestMoveArray = bestMoveScore.ToArray();
            var weightArray = currentWeight.ToArray();
            
            var gradientHypothenuse = Math.Pow(bestMoveArray.Aggregate(0.0, (result, element) => result + element * element), 0.5);
            for (int i = 0; i < bestMoveArray.Length; i++)
            {
                newWeight.Add(weightArray[i] + 0.5 * (bestMoveArray[i] / gradientHypothenuse));  
            }

            return NormalizeData(newWeight).ToList(); 
        }

        private static IEnumerable<double> NormalizeData(IEnumerable<double> list)
        {
            var game = list as double[] ?? list.ToArray();
            var sum = game.Sum(x => Math.Abs(x));
            return game.Select(x => Math.Abs(x/sum));
        }
    }
}